//**********
// 原型链继承
// 使用原型链的原理：让原型对象等于另一个类型的实例
// 构造一个构造函数Animal
// 任何函数只要通过new操作符来调用，那它就可以作为构造函数
function Animal() {
  this.colors = ["black", "white"];
}
// 往函数Animal原型上挂载一个方法返回颜色
Animal.prototype.getColor = function () {
  return this.colors;
};

//  构造一个函数Dog
function Dog() {}
//  此处把Animal实例赋给Dog的原型，当调用构造函数创建一个新实例后，该实例的内部将包含一个指针，指向构造函数的原型对象。
Dog.prototype = new Animal();
//console.log(Animal.prototype); //Animal { getColor: [Function] }
//console.log(Dog.prototype); //Animal { colors: [ 'black', 'white' ] }
//console.log(Dog.prototype.colors); //[ 'black', 'white' ]
//console.log(Dog.prototype.getColor()); //[ 'black', 'white' ]
let dog1 = new Dog();
dog1.colors.push("brown");
let dog2 = new Dog();
console.log(dog2.colors); //[ 'black', 'white', 'brown' ]
console.log(dog2.getColor()); //[ 'black', 'white', 'brown' ]

// 原型链继承存在的问题：
// 问题1： 原型中包含的引用类型属性将被所有实例共享
// 问题2：子类在实例化的时候不能给父类构造函数传参

//**********
// 借用构造函数实现继承
function Animal(name) {
  this.name = name;
  this.getName = function () {
    return this.name;
  };
}
function Dog(name) {
  //继承到了父类animal的属性
  Animal.call(this, name);
}
Dog.prototype = new Animal();
let dog3 = new Dog("brown");
let dog4 = new Dog();
console.log(dog3.name); // brown
console.log(dog4.name); // undefined
console.log(dog3.getName()); // brown
console.log(dog4.getName()); // undefined
// 借用构造函数实现继承解决了原型链继承的 2 个问题：
// 引用类型共享问题以及传参问题。
// 但是由于方法必须定义在构造函数中，所以会导致每次创建子类实例都会创建一遍方法。

//**********
// 组合继承
// 组合继承结合了原型链和盗用构造函数，将两者的优点集中了起来。
// 基本的思路是使用原型链继承原型上的属性和方法，而通过盗用构造函数继承实例属性。
// 这样既可以把方法定义在原型上以实现重用，又可以让每个实例都有自己的属性。
function Animal(name) {
  this.name = name;
  this.colors = ["black", "white"];
}
Animal.prototype.getName = function () {
  return this.name;
};

function Dog(name, age) {
  Animal.call(this, name);
  this.age = age;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

let dog5 = new Dog("彤彤", 3);
dog5.colors.push("brown");
let dog6 = new Dog("俊俊", 2);
console.log(dog5); //Dog {name: "彤彤", colors: Array(3), age: 3}
//age: 3
//colors: (3) ["black", "white", "brown"]
//name: "彤彤"
//__proto__: Animal
console.log(dog6); //Dog {name: "俊俊", colors: Array(2), age: 2}
//age: 2
//colors: (2) ["black", "white"]
//name: "俊俊"
//__proto__: Animal

//**********
// 寄生式组合继承
// 组合继承已经相对完善了，但还是存在问题，它的问题就是调用了 2 次父类构造函数。
// 第一次是在 new Animal()，第二次是在 Animal.call() 这里。
// 所以解决方案就是不直接调用父类构造函数给子类原型赋值
// 而是通过获取父类原型的副本。
function Animal(name) {
  this.name = name;
  this.colors = ["black", "white"];
}
Animal.prototype.getName = function () {
  return this.name;
};

function Dog(name, age) {
  Animal.call(this, name);
  this.age = age;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

let dog7 = new Dog("彤彤", 3);
dog7.colors.push("brown");
let dog8 = new Dog("俊俊", 2);
console.log(dog7);
//Dog {name: "彤彤", colors: Array(3), age: 3
//age: 3
//colors: (3) ["black", "white", "brown"]
//name: "彤彤"
//__proto__: Animal
console.log(dog8);
//Dog {name: "俊俊", colors: Array(2), age: 2}
//age: 2
//colors: (2) ["black", "white"]
//name: "俊俊"
//__proto__: Animal
